package com.xiyu.service.service.elder;

import com.xiyu.service.enums.elder.register.ElderCheckInStatusEnum;
import com.xiyu.service.enums.fee.category.FeeCategoryTypeEnum;
import com.xiyu.service.enums.fee.deposit.FeeDepositDetailStatusEnum;
import com.xiyu.service.enums.fee.recharge.FeeRechargeDetailStatusEnum;
import com.xiyu.service.model.elder.info.ElderInfo;
import com.xiyu.service.model.elder.info.ElderInfoDraft;
import com.xiyu.service.model.fee.category.FeeCategory;
import com.xiyu.service.model.fee.deposit.FeeDeposit;
import com.xiyu.service.model.fee.deposit.FeeDepositDetail;
import com.xiyu.service.model.fee.deposit.FeeDepositDetailDraft;
import com.xiyu.service.model.fee.deposit.FeeDepositDraft;
import com.xiyu.service.model.fee.recharge.*;
import com.xiyu.service.repository.elder.info.ElderInfoRepository;
import com.xiyu.service.repository.fee.category.FeeCategoryRepository;
import com.xiyu.service.repository.fee.deposit.FeeDepositDetailRepository;
import com.xiyu.service.repository.fee.deposit.FeeDepositRepository;
import com.xiyu.service.repository.fee.recharge.FeeRechargeDetailRepository;
import com.xiyu.service.repository.fee.recharge.FeeRechargeFlowRepository;
import com.xiyu.service.repository.fee.recharge.FeeRechargeRepository;
import com.xiyu.service.vo.elder.checkIn.CheckInUpdateStatusInput;
import com.xiyu.service.util.entity.EntityUtils;
import com.xiyu.service.model.elder.register.ElderCheckIn;
import com.xiyu.service.model.elder.register.ElderCheckInDraft;
import com.xiyu.service.model.elder.register.ElderCheckInFee;
import com.xiyu.service.model.elder.register.ElderCheckInProps;
import com.xiyu.service.repository.elder.register.ElderCheckInFeeRepository;
import com.xiyu.service.repository.elder.register.ElderCheckInRepository;
import com.xiyu.service.service.infra.data.DictNoService;
import com.xiyu.service.vo.elder.checkIn.CheckInGetOutput;
import com.xiyu.service.vo.elder.checkIn.CheckInUpdateInput;
import com.xiyu.service.vo.elder.checkIn.CheckInCreateInput;
import com.xiyu.service.vo.elder.checkIn.CheckInQueryOutput;
import com.xiyu.service.vo.elder.checkIn.CheckInQueryInput;
import org.babyfish.jimmer.DraftObjects;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;

import org.springframework.validation.annotation.Validated;
import org.babyfish.jimmer.Page;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

import org.springframework.transaction.annotation.Transactional;
import com.xiyu.service.framework.web.web.core.pojo.PageResult;
import com.xiyu.service.convert.elder.CheckInConvert;

import static com.xiyu.service.framework.exception.util.ServiceExceptionUtil.exception;
import static com.xiyu.service.errorCode.elder.CheckInErrorCode.*;

/**
 * 入住登记 Service 实现类
 */
@Service
@Validated
public class CheckInServiceImpl implements CheckInService {

    @Resource
    private ElderCheckInRepository elderCheckInRepository;

    @Resource
    private ElderCheckInFeeRepository elderCheckInFeeRepository;

    @Resource
    private ElderInfoRepository elderInfoRepository;

    @Resource
    private DictNoService dictNoService;

    @Resource
    private FeeCategoryRepository feeCategoryRepository;

    @Resource
    private FeeDepositRepository feeDepositRepository;

    @Resource
    private FeeDepositDetailRepository feeDepositDetailRepository;

    @Resource
    private FeeRechargeRepository feeRechargeRepository;

    @Resource
    private FeeRechargeFlowRepository feeRechargeFlowRepository;

    @Resource
    private FeeRechargeDetailRepository feeRechargeDetailRepository;

    @Override
    public PageResult<CheckInQueryOutput> query(CheckInQueryInput inputVO) {
        Page<ElderCheckIn> checkInPage = elderCheckInRepository.pageQuery(inputVO);
        List<CheckInQueryOutput> checkInList = CheckInConvert.INSTANCE.queryPageOutputConvert(checkInPage.getRows());
        return new PageResult<>(checkInList, checkInPage.getTotalRowCount());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long create(CheckInCreateInput inputVO) {
        ElderCheckIn newCheckIn = CheckInConvert.INSTANCE.createInputConvert(inputVO);
        newCheckIn = ElderCheckInDraft.$.produce(newCheckIn, draft -> {
            draft.setCheckInNo(dictNoService.produceNo("checkIn"));
        });
        newCheckIn = elderCheckInRepository.insert(newCheckIn);
        return newCheckIn.id();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean update(CheckInUpdateInput inputVO) {
        Optional<ElderCheckIn> opOldCheckIn = elderCheckInRepository.findById(inputVO.getId());
        if(!opOldCheckIn.isPresent()){
            throw exception(ELDER_CHECK_IN_NOT_EXISTS);
        }
        for(CheckInUpdateInput.fee fee : inputVO.getFees()){
            if(Objects.equals(fee.getOperateType(), "delete")){
                elderCheckInFeeRepository.deleteById(fee.getId());
            }else if(Objects.equals(fee.getOperateType(), "new")){
                ElderCheckInFee newFee = CheckInConvert.INSTANCE.updateInputConvert(fee);
                elderCheckInFeeRepository.insert(newFee);
            }else{
                ElderCheckInFee updateFee = CheckInConvert.INSTANCE.updateInputConvert(fee);
                Optional<ElderCheckInFee> opOldFee = elderCheckInFeeRepository.findById(fee.getId());
                if(!opOldFee.isPresent()){
                    throw exception(ELDER_CHECK_IN_FEE_NOT_EXISTS);
                }
                if (!EntityUtils.isEquals(opOldFee.get(), updateFee)){
                    elderCheckInFeeRepository.update(updateFee);
                }
            }
        }
        ElderCheckIn updateCheckIn = CheckInConvert.INSTANCE.updateInputConvert(inputVO);
        updateCheckIn = ElderCheckInDraft.$.produce(updateCheckIn, draft -> {
            DraftObjects.unload(draft, ElderCheckInProps.FEES);
        });
        elderCheckInRepository.update(updateCheckIn);
        return true;
    }

    @Override
    public CheckInGetOutput get(Long id) {
        Optional<ElderCheckIn> opCheckIn = elderCheckInRepository.findDetailById(id);
        if(!opCheckIn.isPresent()){
            throw exception(ELDER_CHECK_IN_NOT_EXISTS);
        }
        return CheckInConvert.INSTANCE.getOutputConvert(opCheckIn.get());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateStatus(CheckInUpdateStatusInput inputVO) {
        Optional<ElderCheckIn> optionalElderCheckIn = elderCheckInRepository.findById(inputVO.getId());
        if(!optionalElderCheckIn.isPresent()){
            throw exception(ELDER_CHECK_IN_NOT_EXISTS);
        }
        if(inputVO.getStatus().equals(ElderCheckInStatusEnum.CANCEL.getValue())){
            if(!optionalElderCheckIn.get().status().equals(ElderCheckInStatusEnum.TO_BE_CHECK_IN.getValue())){
                throw exception(ELDER_CHECK_IN_TO_BE_CHECK_CAN_CANCEL);
            }
        }
        if(inputVO.getStatus().equals(ElderCheckInStatusEnum.CHECKED_IN.getValue())){
            if(!optionalElderCheckIn.get().status().equals(ElderCheckInStatusEnum.TO_BE_CHECK_IN.getValue())){
                throw exception(ELDER_CHECK_IN_TO_BE_CHECK_CAN_CHECKED);
            }

            Optional<ElderCheckIn> optionalExistElderInfo = elderCheckInRepository.findByElderIdAndStatus(
                    optionalElderCheckIn.get().elderId(), ElderCheckInStatusEnum.CHECKED_IN.getValue());
            if(optionalExistElderInfo.isPresent()){
                throw exception(ELDER_CHECK_IN_ALREADY_ELDER);
            }

            Optional<ElderInfo> optionalElderInfo = elderInfoRepository.findById(Objects.requireNonNull(optionalElderCheckIn.get().elderId()));
            if(!optionalElderInfo.isPresent()){
                throw exception(ELDER_CHECK_IN_NOT_ELDER);
            }
            ElderInfo updateElderInfo = ElderInfoDraft.$.produce(optionalElderInfo.get(),draft -> {
                draft.setNursePlanId(optionalElderCheckIn.get().nursePlanId())
                        .setBedInfoId(optionalElderCheckIn.get().bedInfoId())
                        .setRestaurantComboId(optionalElderCheckIn.get().restaurantComboId())
                        .setCheckinDate(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            });
            elderInfoRepository.update(updateElderInfo);

            FeeDeposit feeDeposit;
            Optional<FeeDeposit> optionalFeeDeposit = feeDepositRepository.findByElderId(optionalElderCheckIn.get().elderId());
            if(!optionalFeeDeposit.isPresent()){
                feeDeposit = FeeDepositDraft.$.produce(draft -> {
                    draft.setElderId(optionalElderCheckIn.get().elderId()).setTotalDeposit(BigDecimal.ZERO);
                });
                feeDeposit = feeDepositRepository.insert(feeDeposit);
            } else {
                feeDeposit = optionalFeeDeposit.get();
            }

            FeeRecharge feeRecharge;
            Optional<FeeRecharge> optionalFeeRecharge = feeRechargeRepository.findByElderId(optionalElderCheckIn.get().elderId());
            if(!optionalFeeRecharge.isPresent()){
                feeRecharge = FeeRechargeDraft.$.produce(draft -> {
                    draft.setElderId(optionalElderCheckIn.get().elderId()).setTotalRecharge(BigDecimal.ZERO)
                            .setTotalDeposit(BigDecimal.ZERO);
                });
                feeRecharge = feeRechargeRepository.insert(feeRecharge);
            }else{
                feeRecharge = optionalFeeRecharge.get();
            }

            Optional<ElderCheckIn> optionalElderCheckInDetail = elderCheckInRepository.findDetailById(inputVO.getId());
            if(!optionalElderCheckInDetail.isPresent()){
                throw exception(ELDER_CHECK_IN_NOT_EXISTS);
            }
            for(ElderCheckInFee fee : optionalElderCheckInDetail.get().fees()){
                Optional<FeeCategory> optionalFeeCategory = feeCategoryRepository.findById(Objects.requireNonNull(fee.feeCategoryId()));
                if(!optionalFeeCategory.isPresent()){
                    throw exception(ELDER_CHECK_IN_FEE_CATEGORY_NOT_EXISTS);
                }

                if(optionalFeeCategory.get().type().equals(FeeCategoryTypeEnum.DEPOSIT.getValue())){

                    feeDeposit = FeeDepositDraft.$.produce(feeDeposit, draft -> {
                        draft.setTotalDeposit(draft.totalDeposit().add(fee.fee()));
                    });

                    FeeDepositDetail feeDepositDetail = FeeDepositDetailDraft.$.produce(draft -> {
                        draft.setStatus(FeeDepositDetailStatusEnum.PAID_FULL.getValue())
                                .setDepositFee(fee.fee()).setDescription(fee.remark()).setElderId(optionalElderCheckIn.get().elderId())
                                .setFeeCategoryId(fee.feeCategoryId()).setPayType(optionalElderCheckIn.get().payType())
                                .setPayTime(LocalDateTime.now());
                    });

                    feeDepositDetailRepository.insert(feeDepositDetail);
                }else{
                    feeRecharge = FeeRechargeDraft.$.produce(feeRecharge, draft -> {
                        draft.setTotalRecharge(draft.totalRecharge().add(fee.fee()));
                    });
                    FeeRechargeDetail feeRechargeDetail = FeeRechargeDetailDraft.$.produce(draft -> {
                        draft.setStatus(FeeRechargeDetailStatusEnum.PAID_FULL.getValue())
                                .setPayTime(LocalDateTime.now()).setElderId(optionalElderCheckIn.get().elderId()).setFeeCategoryId(fee.feeCategoryId())
                                .setRechargeFee(fee.fee()).setDescription(fee.remark()).setPayType(optionalElderCheckIn.get().payType());
                    });

                    feeRechargeDetailRepository.insert(feeRechargeDetail);

                    FeeRecharge finalFeeRecharge = feeRecharge;
                    FeeRechargeFlow feeRechargeFlow = FeeRechargeFlowDraft.$.produce(draft -> {
                        draft.setElderId(optionalElderCheckIn.get().elderId()).setFeeCategoryId(fee.feeCategoryId())
                                .setFee(fee.fee()).setRemark(fee.remark()).setBeforeFee(finalFeeRecharge.totalRecharge().subtract(fee.fee()))
                                .setFee(fee.fee()).setAfterFee(finalFeeRecharge.totalRecharge());
                    });
                    feeRechargeFlowRepository.insert(feeRechargeFlow);
                }
            }
            feeDepositRepository.update(feeDeposit);
            feeRechargeRepository.update(feeRecharge);
        }
        ElderCheckIn updateCheckIn = ElderCheckInDraft.$.produce(optionalElderCheckIn.get(), draft -> {
            draft.setStatus(inputVO.getStatus());
        });
        elderCheckInRepository.update(updateCheckIn);
        return true;
    }

}
